/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edison.jcr;

/**
 * <code>NameFactoryImpl</code>...
 */
public class NameFactory {

	public static Name create(String namespaceURI, String localName) throws IllegalArgumentException {
		if (namespaceURI == null) {
			throw new IllegalArgumentException("No namespaceURI specified");
		}
		if (localName == null) {
			throw new IllegalArgumentException("No localName specified");
		}
		return new Name(namespaceURI, localName);
	}

	public static Name create(String nameString) throws IllegalArgumentException {
		if (nameString == null || "".equals(nameString)) {
			throw new IllegalArgumentException("No Name literal specified");
		}
		if (nameString.charAt(0) != '{') {
			throw new IllegalArgumentException("Invalid Name literal: " + nameString);
		}
		int i = nameString.indexOf('}');
		if (i == -1) {
			throw new IllegalArgumentException("Invalid Name literal: " + nameString);
		}
		if (i == nameString.length() - 1) {
			throw new IllegalArgumentException("Invalid Name literal: " + nameString);
		}
		return new Name(nameString.substring(1, i), nameString.substring(i + 1));
	}	
	
	public static class Name {

		/** The empty namespace uri */
		private static final String EMPTY = "".intern();

		/** The memorized hash code of this name. */
		private transient int hash;

		/** The memorized string representation of this name. */
		private transient String string;

		/** The internalized namespace URI of this name. */
		private final String namespaceURI;

		/** The local part of this name. */
		private final String localName;

		private Name(String namespaceURI, String localName) {
			// internalize namespaceURI to improve performance of comparisons.
			if (namespaceURI.length() == 0) {
				// see JCR-2464
				this.namespaceURI = EMPTY;
			} else {
				this.namespaceURI = namespaceURI.intern();
			}
			// localName is not internalized in order not to risk huge perm
			// space for large repositories
			this.localName = localName;
			hash = 0;
		}

	
		public String getLocalName() {
			return localName;
		}
		
		public String getNamespaceURI() {
			return namespaceURI;
		}
		
		/**
		 * Returns the string representation of this <code>Name</code> in the
		 * following format:
		 * <p/>
		 * <code><b>{</b>namespaceURI<b>}</b>localName</code>
		 * 
		 * @return the string representation of this <code>Name</code>.
		 * @see NameFactory#create(String)
		 * @see Object#toString()
		 */
		@Override
		public String toString() {
			// Name is immutable, we can store the string representation
			if (string == null) {
				string = '{' + namespaceURI + '}' + localName;
			}
			return string;
		}

		/**
		 * Compares two names for equality. Returns <code>true</code> if the
		 * given object is a <code>Name</code> and has the same namespace URI
		 * and local part as this <code>Name</code>.
		 * 
		 * @param obj
		 *            the object to compare.
		 * @return <code>true</code> if the object is equal to this
		 *         <code>Name</code>, <code>false</code> otherwise.
		 * @see Object#equals(Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj instanceof Name) {
				Name other = (Name) obj;
				// we can use == operator for namespaceURI since it is
				// internalized
				return namespaceURI == other.namespaceURI && localName.equals(other.localName);
			}
			// some other Name implementation
			if (obj instanceof Name) {
				Name other = (Name) obj;
				return namespaceURI.equals(other.getNamespaceURI()) && localName.equals(other.getLocalName());
			}
			return false;
		}

		/**
		 * Returns the hash code of this name. The hash code is computed from
		 * the namespace URI and local part of the name and memorized for better
		 * performance.
		 * 
		 * @return hash code
		 * @see Object#hashCode()
		 */
		@Override
		public int hashCode() {
			// Name is immutable, we can store the computed hash code value
			int h = hash;
			if (h == 0) {
				h = 17;
				h = 37 * h + namespaceURI.hashCode();
				h = 37 * h + localName.hashCode();
				hash = h;
			}
			return h;
		}	

		// -----------------------------------------------------< Comparable
		// >---
		/**
		 * Compares two <code>Name</code>s.
		 * 
		 * @param o
		 *            the object to compare.
		 * @return comparison result
		 * @throws ClassCastException
		 *             if the given object is not a <code>Name</code>.
		 * @see Comparable#compareTo(Object)
		 */
		public int compareTo(Object o) {
			if (this == o) {
				return 0;
			}
			Name other = (Name) o;
			if (namespaceURI.equals(other.getNamespaceURI())) {
				return localName.compareTo(other.getLocalName());
			} else {
				return namespaceURI.compareTo(other.getNamespaceURI());
			}
		}

		// ---------------------------------------------------< Serializable
		// >---
		/**
		 * Creates a new <code>Name</code> instance using the proper constructor
		 * during deserialization in order to make sure that internalized
		 * strings are used where appropriate.
		 */
		private Object readResolve() {
			return new Name(namespaceURI, localName);
		}
	}

}
